import CommonService from './common-service';

export class ValidatorPattern {
  // The string of regular expression pattern.
  pattern: string = '';

  // The string of error message which returned to field validator.
  // When pattern matches in matchCheck = true or pattern does NOT match in matchCheck = false the errorMessage is not null/empty.
  errorMessage: string | null = null;

  // True means check if the pattern matches. False means check if the pattern does NOT matche.
  matchCheck?: boolean = true;
}

export class ExpressionValidationResult {
  expressionResult?: number | null;
  errorMessage?: string | null;
}

/**
* This is the validator for form text field.
*
* All kind of validators based on provided properties.
* Example:
* - If [length] is set then [_checkLength] will be applied.
* - If [minLength] is set then [_checkMinLength] will be applied.
* - If [minLength] and [maxLength] are both set then [_checkLengthRange] will be applied.
* All you need to do is setting these properties based on your needs.
*
* [patterns] is for any no predefined validation rules.
* The map key is regular expression pattern. The value is the error message for such validation.
* If the pattern matches that means error.
 */
export default class FormTextFieldValidator {
  readonly nullOrEmptyValueErrorMessage: string = '%s不可为空';
  readonly notPositiveNumberErrorMessage: string = '%s不是合法正整数';
  readonly notNumberErrorMessage: string = '%s不是合法数字';
  readonly sameValueErrorMessage: string = '%s和%s的值必须相同';
  readonly lengthErrorMessage: string = '%s长度必须为%s';
  readonly minValueErrorMessage: string = '%s的最小值为%s';
  readonly maxValueErrorMessage: string = '%s的最大值为%s';
  readonly valueRangeErrorMessage: string = '%s的值需在%s和%s之间';
  readonly minLengthErrorMessage: string = '%s值的长度不能小于%s';
  readonly maxLengthErrorMessage: string = '%s值的长度不能大于%s';
  readonly lengthRangeErrorMessage: string = '%s值的长度需在%s和%s之间';
  readonly startsWithErrorMessage: string = '%s的值必须以%s开头';

  readonly sprintf = require('sprintf-js').sprintf;

  label: string = '';
  value: any | null;

  // These for check same value.
  label2: string = '';
  value2: any | null;

  isRequired: boolean = true;
  isPositiveNumber: boolean = false;
  isNumber: boolean = false;
  length?: number | null;
  minLength?: number | null;
  maxLength?: number | null;
  minValue?: number | null;
  maxValue?: number | null;
  prefix?: string | null;
  patterns?: ValidatorPattern[] | null;


  /// Starts to validate.
  validate(): string | null {
    let result: string | null = null;

    if (this.isRequired!) {
      result = this.checkNullOrEmpty(this.label, this.value);
    }

    // The input method might still can input emotion symbol even the keyboard type is number.
    // That's why we still need to do check.
    if (CommonService.isNullOrUndefinedOrEmptyString(result) && this.isPositiveNumber) {
      result = this.checkPositiveNumber(this.label, this.value);
    }

    // The input method might still can input emotion symbol even the keyboard type is number.
    // That's why we still need to do check.
    if (CommonService.isNullOrUndefinedOrEmptyString(result) && this.isNumber) {
      result = this.checkNumber(this.label, this.value);
    }

    if (CommonService.isNullOrUndefinedOrEmptyString(result) &&
      !CommonService.isNullOrUndefinedOrEmptyString(this.value2)) {
      result = this.checkSameValue(this.label, this.value, this.label2, this.value2);
    }

    if (CommonService.isNullOrUndefinedOrEmptyString(result) && this.length != null) {
      result = this.checkLength(this.label, this.value, this.length);
    }

    if (CommonService.isNullOrUndefinedOrEmptyString(result) &&
      this.minLength != null &&
      this.maxLength == null) {
      result = this.checkMinLength(this.label, this.value, this.minLength!);
    }

    if (CommonService.isNullOrUndefinedOrEmptyString(result) &&
      this.minLength == null &&
      this.maxLength != null) {
      result = this.checkMaxLength(this.label, this.value, this.maxLength!);
    }

    if (CommonService.isNullOrUndefinedOrEmptyString(result) &&
      this.minLength != null &&
      this.maxLength != null) {
      result = this.checkLengthRange(this.label, this.value, this.minLength!, this.maxLength!);
    }

    if (CommonService.isNullOrUndefinedOrEmptyString(result) &&
      this.minValue != null &&
      this.maxValue == null) {
      result = this.checkMinValue(this.label, this.value, this.minValue!);
    }

    if (CommonService.isNullOrUndefinedOrEmptyString(result) &&
      this.minValue == null &&
      this.maxValue != null) {
      result = this.checkMaxValue(this.label, this.value, this.maxValue!);
    }

    if (CommonService.isNullOrUndefinedOrEmptyString(result) &&
      this.minValue != null &&
      this.maxValue != null) {
      result = this.checkValueRange(this.label, this.value, this.minValue!, this.maxValue!);
    }

    if (CommonService.isNullOrUndefinedOrEmptyString(result) &&
      !CommonService.isNullOrUndefinedOrEmptyString(this.prefix)) {
      result = this.checkStartsWith(this.label, this.value, this.prefix!);
    }

    if (CommonService.isNullOrUndefinedOrEmptyString(result) && this.patterns != null) {
      result = this.checkOnPatterns(this.label, this.value, this.patterns!);
    }

    return result;
  }

  /**
   * Validates if the input [value] is null or empty.
   */
  checkNullOrEmpty(label: string, value: any): string | null {
    if (value == null || (value?.toString()?.trim() === '')) {
      return this.sprintf(this.nullOrEmptyValueErrorMessage, label);
    }

    return null;
  }

  /**
   * Validates if the input [value] is a valid positive number.
   */
  checkPositiveNumber(label: string, value: string): string | null {
    if (!CommonService.checkPositiveNumber(value)) {
      return this.sprintf(this.notPositiveNumberErrorMessage, label);
    }

    return null;
  }

  /**
   * Validates if the input [value] is a valid number.
   */
  checkNumber(label: string, value: string): string | null {
    if (!CommonService.checkNumber(value)) {
      return this.sprintf(this.notNumberErrorMessage, label);
    }

    return null;
  }

  /**
   * Validates if the input [value1] and [value2] have the same value.
   */
  checkSameValue(
    label1: string, value1: any, label2: string, value2: any): string | null {
    if (value1 !== value2) {
      return this.sprintf(this.sameValueErrorMessage, label1, label2);
    }

    return null;
  }

  /**
   * Validates if the input [value.length] equals [length].
   */
  checkLength(label: string, value: any, length: number | null): string | null {
    if (value === null || (value.toString().length !== length)) {
      return this.sprintf(this.lengthErrorMessage, label, length);
    }

    return null;
  }

  /**
   * Validates if the input [value.length] is less than [minLength].
   */
  checkMinLength(label: string, value: string, minLength: number): string | null {
    if (value.length < minLength) {
      return this.sprintf(this.minLengthErrorMessage, label, minLength);
    }

    return null;
  }

  /**
   * Validates if the input [value.length] is greater than [maxLength].
   */
  checkMaxLength(label: string, value: string, maxLength: number): string | null {
    if (value.length > maxLength) {
      return this.sprintf(this.maxLengthErrorMessage, label, maxLength);
    }

    return null;
  }

  /**
   * Validates if the input [value.length] is between [minLength] and [maxLength]. 
   */
  checkLengthRange(
    label: string, value: string, minLength: number, maxLength: number): string | null {
    if (value.length < minLength || value.length > maxLength) {
      return this.sprintf(this.lengthRangeErrorMessage, label, minLength, maxLength);
    }

    return null;
  }

  /**
   * Validates if the input [value] is less than [minValue].
   */
  checkMinValue(label: string, value: number, minValue: number): string | null {
    if (value < minValue) {
      return this.sprintf(this.minValueErrorMessage, label, minValue);
    }

    return null;
  }

  /**
   * Validates if the input [value] is greater than [maxValue].
   */
  checkMaxValue(label: string, value: number, maxValue: number): string | null {
    if (value > maxValue) {
      return this.sprintf(this.maxValueErrorMessage, label, maxValue);
    }

    return null;
  }

  /**
   * Validates if the input [value] is between [minValue] and [maxValue].
   */
  checkValueRange(
    label: string, value: number, minValue: number, maxValue: number): string | null {
    if (value < minValue || value > maxValue) {
      return this.sprintf(this.valueRangeErrorMessage, label, minValue, maxValue);
    }

    return null;
  }

  /**
   * Validates if the input [value] starts with [prefix].
   */
  checkStartsWith(label: string, value: string, prefix: string): string | null {
    if (!value.startsWith(prefix)) {
      return this.sprintf(this.startsWithErrorMessage, label, prefix);
    }

    return null;
  }

  /**
  * Iterates the [patterns] checks on each item's key which is regular expression.
  * This is for any no predefined validation usage.
  * If the pattern matches that means error.
   */
  checkOnPatterns(
    label: string, value: string, patterns: ValidatorPattern[]): string | null {
    let errorMessage: string | null = null;
    let item: ValidatorPattern;

    for (var i = 0; i < patterns.length; i++) {
      item = patterns[i];

      if ((item.matchCheck && new RegExp(item.pattern!).test(value!)) ||
        (!item.matchCheck && !new RegExp(item.pattern!).test(value!))) {
        errorMessage = item.errorMessage;
        break;
      }
    }

    return errorMessage;
  }
}